﻿// Inner Fire 游戏引擎库
// DxDraw 渲染器绘制功能
//
// Copyright (c) 2025 by 尔西大帝. All rights reserved.
//
// 用于DirectX的渲染器。
// 此文件分装用于绘制渲染的功能。
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2025-04-23

#include "dx_draw.h"
#include "dx_graphics.h"
#include "graphics/imath.h"
#include <game/core/timer.h>

namespace ifire::dx {

DxDraw::DxDraw(DxGraphics* dx) : dx_(dx), current_piepline(nullptr) {}

void DxDraw::Draw() {
  // 得到当前帧的命令分配器
  auto allocator = dx_->CurrentFrameRes()->allocator.Get();
  cmd = dx_->CommandList();

  // 重置当前生效的Pipeline
  current_piepline = nullptr;

  // 复用记录命令所用的内存。只有当GPU中的命令列表执行完毕后，才能对其进行重置。
  HASSERT(allocator->Reset());

  // 通过ExecuteCommandList将命令列表加入命令队列之后，便可对其重置
  if (dx_->IsWireMode()) {
    HASSERT(cmd->Reset(allocator, GetPipeline("OpaqueWire")));
  } else {
    HASSERT(cmd->Reset(allocator, GetPipeline("Opaque")));
  }

  // 设置常量缓冲区（这里改了，是使用srv了）
  ID3D12DescriptorHeap* descriptor_heaps[] = {
      dx_->GetDescriptor().srv_heap_.Get()};
  cmd->SetDescriptorHeaps(_countof(descriptor_heaps), descriptor_heaps);

  // 在这里执行顶点的更新。因为产生的图要做为后续的输入
  UpdateWavesGPU();

  //  设置根签名
  cmd->SetGraphicsRootSignature(dx_->GetRootsign().mStandard.Get());

  /////////////////////////////////////////////////////////////////////////////
  // 阴影 PASS
  /////////////////////////////////////////////////////////////////////////////

  // 绑定在此场景中使用的所有材质。对于结构化缓冲区，我们可以绕过堆集，并将其设置为根描述符。
  auto mat_buffer = dx_->CurrentFrameRes()->material_buffer->Resource();
  cmd->SetGraphicsRootShaderResourceView(
      ShaderSlot::MATERIAL, mat_buffer->GetGPUVirtualAddress());

  // 将 空（null）SRV 与ShadowPass绑定
  // 这东西是一个CubeMap，所以天空盒(SKYBOX)的位置可以接受这个绑定
  cmd->SetGraphicsRootDescriptorTable(
      ShaderSlot::SKYBOX, dx_->GetDescriptor().mNullCube);

  // 绘制阴影贴图
  DrawSceneToShadowMap();

  /////////////////////////////////////////////////////////////////////////////
  // SSAO Pass
  /////////////////////////////////////////////////////////////////////////////
  if (dx_->GetPostProcessing().GetSsaoEnable()) {
    // 法线(Normal)/深度(Depth) PASS
    DrawNormalsAndDepth();

    // 计算 SSAO.
    cmd->SetGraphicsRootSignature(dx_->GetRootsign().mSsao.Get());
    dx_->GetSsao()->ComputeSsao(cmd, dx_->CurrentFrameRes(), 3);
  }

  /////////////////////////////////////////////////////////////////////////////
  // Main Pass
  /////////////////////////////////////////////////////////////////////////////

  // 将默认的rootsign设置回来
  cmd->SetGraphicsRootSignature(dx_->GetRootsign().mStandard.Get());

  // 设置视口和剪裁
  cmd->RSSetViewports(1, &dx_->GetView().GetScreenViewport());
  cmd->RSSetScissorRects(1, &dx_->GetView().GetScissorRect());

  // 将离屏纹理更改为用作渲染目标输出。
  // 这里和以前的渲染不一样，因为都是用离屏纹理，所以不能调用原来的BackBuffer了。
  auto offscreen_render = CD3DX12_RESOURCE_BARRIER::Transition(
      dx_->GetOffscreenRt()->Resource(), D3D12_RESOURCE_STATE_GENERIC_READ,
      D3D12_RESOURCE_STATE_RENDER_TARGET);
  cmd->ResourceBarrier(1, &offscreen_render);

  // 得到后备缓冲区
  auto ds_view = dx_->GetDepthStencilView();

  cmd->ClearRenderTargetView(dx_->GetOffscreenRt()->mhCpuRtv,
      dx_->GetOffscreenRt()->clearValue.Color, 0, nullptr);
  cmd->ClearDepthStencilView(ds_view,
      D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);
  // Specify the buffers we are going to render to.
  cmd->OMSetRenderTargets(1, &dx_->GetOffscreenRt()->mhCpuRtv, true, &ds_view);

  // 使用MainPass绘制不透明的常规对象
  auto pass_cb = dx_->CurrentFrameRes()->pass_cb->Resource();
  cmd->SetGraphicsRootConstantBufferView(
      ShaderSlot::MAIN_PASS, pass_cb->GetGPUVirtualAddress());

  // 这里，这玩意用一个表来一次设置2个项目！这就是为什么我的SLOT出问题的原因。
  cmd->SetGraphicsRootDescriptorTable(
      ShaderSlot::SKYBOX, dx_->GetDescriptor().mSkyBox);

  // 绘制常规不透明物体
  if (dx_->IsWireMode()) {
    // 如果是绘制线框，则没有什么透明和不透明物体的区别，直接在这里一并绘制
    DrawLayer(RenderLayer::Opaque, "OpaqueWire");
    DrawLayer(RenderLayer::Transparent, "OpaqueWire");
    DrawLayer(RenderLayer::AlphaTransparent, "OpaqueWire");
    DrawLayer(RenderLayer::SkinnedOpaque, "SkinnedOpaqueWire");
    DrawLayer(RenderLayer::SkinnedTransparent, "SkinnedOpaqueWire");
    DrawLayer(RenderLayer::SkinnedAlpha, "SkinnedOpaqueWire");
  } else {
    DrawLayer(RenderLayer::Opaque, "Opaque");
    DrawLayer(RenderLayer::SkinnedOpaque, "SkinnedOpaque");
  }

  // 绘制树
  if (dx_->IsWireMode() == false) {
    DrawLayer(RenderLayer::Tree, "TreeSprites");
  }

  // 绘制AlphaTest对象
  if (dx_->IsWireMode()) {
    DrawLayer(RenderLayer::AlphaTest, "OpaqueWire");
    DrawLayer(RenderLayer::SkinnedAlphaTest, "SkinnedOpaqueWire");
  } else {
    DrawLayer(RenderLayer::AlphaTest, "AlphaTested");
    DrawLayer(RenderLayer::SkinnedAlphaTest, "SkinnedAlphaTested");
  }

  // 绘制粒子(这里暂时设置Pipeline为能够穿透（始终在最前））。所以放置在这个地方绘制。
  dx_->GetInstancedSprite().Draw(cmd);

  // 绘制水体
  auto gpu_waves = GetLayer(RenderLayer::GpuWaves);
  if (gpu_waves.size() > 0) {
    if (dx_->IsWireMode()) {
      cmd->SetPipelineState(GetPipeline("OpaqueWire"));
    } else {
      cmd->SetGraphicsRootDescriptorTable(
          ShaderSlot::DISPLACEMENT, dx_->GetWaves()->DisplacementMap());
      cmd->SetPipelineState(GetPipeline("WavesRender"));
    }
    DrawRenderItems(gpu_waves);
  }

  // 绘制天空盒
  if (dx_->IsWireMode() == false) {
    DrawLayer(RenderLayer::SkyBox, "Sky");
  }

  // 绘制透明物体
  if (!dx_->IsWireMode()) {
    DrawLayer(RenderLayer::Transparent, "Transparent");
    DrawLayer(RenderLayer::AlphaTransparent, "AlphaTransparent");
    DrawLayer(RenderLayer::SkinnedAlpha, "SkinnedAlpha");
    DrawLayer(RenderLayer::SkinnedTransparent, "SkinnedTransparent");
  }

  // 绘制调试图
  if (dx_->GetDebugLayer() != DebugLayer::None) {
    DrawLayer(RenderLayer::Debug, "Debug");
  }

  // 绘制UI（最后才进行）
  if (dx_->IsSobelEnable()) {
    // 改变离屏纹理，将其用于输出
    auto set_input = CD3DX12_RESOURCE_BARRIER::Transition(
        dx_->GetOffscreenRt()->Resource(), D3D12_RESOURCE_STATE_RENDER_TARGET,
        D3D12_RESOURCE_STATE_GENERIC_READ);
    cmd->ResourceBarrier(1, &set_input);
    // 执行索贝尔算子
    dx_->GetSobel()->Execute(cmd, dx_->GetRootsign().mPostProcessing.Get(),
        GetPipeline("Sobel"), dx_->GetOffscreenRt()->mhGpuSrv);

    // 将渲染对象设置回后备缓冲区
    auto back_render =
        CD3DX12_RESOURCE_BARRIER::Transition(dx_->GetBackBuffer(),
            D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
    cmd->ResourceBarrier(1, &back_render);

    // Specify the buffers we are going to render to.
    auto back_buffer = dx_->GetBackBufferView();
    cmd->OMSetRenderTargets(1, &back_buffer, true, &ds_view);

    // 进行混合计算
    cmd->SetGraphicsRootSignature(dx_->GetRootsign().mPostProcessing.Get());
    cmd->SetPipelineState(GetPipeline("Composite"));
    cmd->SetGraphicsRootDescriptorTable(0, dx_->GetOffscreenRt()->mhGpuSrv);
    cmd->SetGraphicsRootDescriptorTable(1, dx_->GetSobel()->OutputSrv());
    DrawFullscreenQuad();

  } else {
    // 绘制UI（最后才进行）
    DrawUI();

    // 直接进行复制

    // 1. 将Offscreen设置为复制源
    auto barrier1 = CD3DX12_RESOURCE_BARRIER::Transition(
        dx_->GetOffscreenRt()->Resource(), D3D12_RESOURCE_STATE_RENDER_TARGET,
        D3D12_RESOURCE_STATE_COPY_SOURCE);
    cmd->ResourceBarrier(1, &barrier1);

    // 2. 将Back Buffer设置为复制目标
    barrier1 = CD3DX12_RESOURCE_BARRIER::Transition(dx_->GetBackBuffer(),
        D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_COPY_DEST);
    cmd->ResourceBarrier(1, &barrier1);

    // 3. 进行资源的复制
    cmd->CopyResource(dx_->GetBackBuffer(), dx_->GetOffscreenRt()->Resource());

    // 4. 将Back Buffer设置回正常的Render Target
    barrier1 = CD3DX12_RESOURCE_BARRIER::Transition(dx_->GetBackBuffer(),
        D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_RENDER_TARGET);
    cmd->ResourceBarrier(1, &barrier1);

    // 5. 将Offscreen设置为正常的Read（因为在Draw开始的时候，他应该是这个状态）
    barrier1 = CD3DX12_RESOURCE_BARRIER::Transition(
        dx_->GetOffscreenRt()->Resource(), D3D12_RESOURCE_STATE_COPY_SOURCE,
        D3D12_RESOURCE_STATE_GENERIC_READ);
    cmd->ResourceBarrier(1, &barrier1);
  }

  // 将资源从绘制转为呈现状态
  auto to_present = CD3DX12_RESOURCE_BARRIER::Transition(dx_->GetBackBuffer(),
      D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
  cmd->ResourceBarrier(1, &to_present);

  // 完成命令的记录
  dx_->ExecuteCommands();
  dx_->GetSwapchain().PresentAndSwap();
  dx_->GetIniter().NextFence();
}

void DxDraw::DrawLayer(RenderLayer layer, const char* pipeline) {
  auto& items = GetLayer(layer);
  if (items.size() > 0) {
    // 如果当前的Pipeline已经生效，则直接绘制，不需要更改
    if (current_piepline == pipeline) {
      DrawRenderItems(items);
    } else {
      auto p = GetPipeline(pipeline);
      if (p) { // 如果没有，则干脆不画，目前调试中可以这么做。以后才整体设置Shader
        cmd->SetPipelineState(p);
        DrawRenderItems(items);
        // 确保生效之后才使用
        current_piepline = pipeline;
      }
    }
  }
}

void DxDraw::DrawRenderItems(const eastl::vector<RenderItem*>& items) {

  // 得到缓冲区（Uploader）
  auto object_cb = dx_->CurrentFrameRes()->object_cb->Resource();
  // auto material_cb = dx_->CurrentFrameRes()->MaterialCB->Resource();

  // 遍历项目
  for (size_t i = 0; i < items.size(); i++) {
    // 得到项目
    auto item = items[i];
    auto material_name = item->material->albedo.c_str();
    if (item->visible == false) {
      continue;
    }

    DrawRenderItem(item, object_cb);
  }
}

void DxDraw::DrawShadowItems(const eastl::vector<RenderItem*>& items) {
  // 得到缓冲区（Uploader）
  auto object_cb = dx_->CurrentFrameRes()->object_cb->Resource();

  // 遍历项目
  for (size_t i = 0; i < items.size(); i++) {
    // 得到项目
    auto item = items[i];
    if (item->cast_shadow == false) {
      continue;
    }

    DrawRenderItem(item, object_cb);
  }
}

void DxDraw::DrawRenderItem(RenderItem* item, ID3D12Resource* object_cb) {

  if (item->visible == false) {
    return;
  }

  constexpr size_t SKINNED_CB_SIZE =
      DxUtils::CalcConstantBufferSize(sizeof(SkinnedConstants));
  auto skinnedCB = dx_->CurrentFrameRes()->skinned_cb->Resource();

  // 绑定顶点与索引
  auto vertex_view = item->geo->VertexBufferView();
  cmd->IASetVertexBuffers(0, 1, &vertex_view);
  auto index_view = item->geo->IndexBufferView();
  cmd->IASetIndexBuffer(&index_view);
  cmd->IASetPrimitiveTopology(item->primitive_type);

  // 设置蒙皮骨骼
  if (item->animator != nullptr) {
    D3D12_GPU_VIRTUAL_ADDRESS skinnedCBAddress =
        skinnedCB->GetGPUVirtualAddress() +
        item->animator->Index() * SKINNED_CB_SIZE;
    cmd->SetGraphicsRootConstantBufferView(
        ShaderSlot::SKINNED_CB, skinnedCBAddress);
  } else {
    cmd->SetGraphicsRootConstantBufferView(ShaderSlot::SKINNED_CB, 0);
  }

  // 如果是天空盒，需要设置CUBEMAP
  if (item->render_layer == RenderLayer::SkyBox) {
    cmd->SetGraphicsRootDescriptorTable(
        ShaderSlot::SKYBOX, dx_->GetDescriptor().mSkyBox);
    cmd->SetGraphicsRootDescriptorTable(ShaderSlot::NORMAL_MAP, //
        dx_->GetDescriptor().mEmptyNormal);
  } else if (item->render_layer == RenderLayer::Tree) {
    // 如果是树，需要设置纹理数组
    cmd->SetGraphicsRootDescriptorTable(ShaderSlot::TEXTURE_ARRAY,
        dx_->GetDescriptor().GetTextureGpu(item->material->diffuse_index));
    cmd->SetGraphicsRootDescriptorTable(ShaderSlot::NORMAL_MAP, //
        dx_->GetDescriptor().mEmptyNormal);
  } else {
    // 设置纹理
    cmd->SetGraphicsRootDescriptorTable(ShaderSlot::TEXTURE,
        dx_->GetDescriptor().GetTextureGpu(item->material->diffuse_index));

    // 设置法线
    if (item->material->normal_index != -1) {
      cmd->SetGraphicsRootDescriptorTable(ShaderSlot::NORMAL_MAP,
          dx_->GetDescriptor().GetTextureGpu(item->material->normal_index));
    } else {
      cmd->SetGraphicsRootDescriptorTable(
          ShaderSlot::NORMAL_MAP, dx_->GetDescriptor().mEmptyNormal);
    }
  }

  // 新式使用根常量缓冲区
  D3D12_GPU_VIRTUAL_ADDRESS obj_cb_address =
      object_cb->GetGPUVirtualAddress() + item->cb_index * OBJECT_CB_SIZE;
  cmd->SetGraphicsRootConstantBufferView(ShaderSlot::OBJECT_CB, obj_cb_address);
  // 进行绘制
  cmd->DrawIndexedInstanced(
      item->index_count, 1, item->index_start, item->vertex_start, 0);
}

void DxDraw::DrawSceneToShadowMap() {
  auto cmd = dx_->CommandList();
  // 设置阴影的视口
  auto viewport = dx_->GetShadow()->Viewport();
  auto scissor = dx_->GetShadow()->ScissorRect();
  cmd->RSSetViewports(1, &viewport);
  cmd->RSSetScissorRects(1, &scissor);

  // 将ShadowMap的状态转为可写
  auto to_write =
      CD3DX12_RESOURCE_BARRIER::Transition(dx_->GetShadow()->Resource(),
          D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_STATE_DEPTH_WRITE);
  cmd->ResourceBarrier(1, &to_write);

  // 清理后备缓冲区的深度信息
  cmd->ClearDepthStencilView(dx_->GetShadow()->mCpuDsv,
      D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);

  // 设置空的渲染目标，因为我们只打算绘制到深度缓冲区。
  // 设置空的渲染目标将禁用颜色写入。
  // 注意，活动的PSO（管线状态对象）也必须指定渲染目标计数为0。
  cmd->OMSetRenderTargets(0, nullptr, false, &dx_->GetShadow()->mCpuDsv);

  // 绑定阴影贴图PASS的传递常量缓冲区。
  auto passCB = dx_->CurrentFrameRes()->pass_cb->Resource();
  auto shadow_pass_index = dx_->CurrentFrameRes()->ShadowPassIndex();
  D3D12_GPU_VIRTUAL_ADDRESS pass_address =
      passCB->GetGPUVirtualAddress() + shadow_pass_index * PASS_CB_SIZE;
  cmd->SetGraphicsRootConstantBufferView(ShaderSlot::MAIN_PASS, pass_address);

  // 设置阴影渲染的Pipeline
  cmd->SetPipelineState(GetPipeline("ShadowOpaque"));

  // 绘制需要绘制阴影的项目
  DrawShadowItems(GetLayer(RenderLayer::Opaque));

  // 将ShadowMap的状态转为可读，以便在Shader中使用
  auto to_read =
      CD3DX12_RESOURCE_BARRIER::Transition(dx_->GetShadow()->Resource(),
          D3D12_RESOURCE_STATE_DEPTH_WRITE, D3D12_RESOURCE_STATE_GENERIC_READ);
  cmd->ResourceBarrier(1, &to_read);
}

void DxDraw::DrawNormalsAndDepth() {
  auto cmd = dx_->CommandList();
  cmd->RSSetViewports(1, &dx_->GetView().GetScreenViewport());
  cmd->RSSetScissorRects(1, &dx_->GetView().GetScissorRect());

  auto normalMap = dx_->GetSsao()->NormalMap();
  auto normalMapRtv = dx_->GetSsao()->NormalMapRtv();

  // Change to RENDER_TARGET.
  auto to_target = CD3DX12_RESOURCE_BARRIER::Transition(normalMap,
      D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_STATE_RENDER_TARGET);
  cmd->ResourceBarrier(1, &to_target);

  // Clear the screen normal map and depth buffer.
  float clearValue[] = {0.0f, 0.0f, 1.0f, 0.0f};
  auto dsv = dx_->GetDepthStencilView();
  cmd->ClearRenderTargetView(normalMapRtv, clearValue, 0, nullptr);
  cmd->ClearDepthStencilView(dsv,
      D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);

  // Specify the buffers we are going to render to.
  cmd->OMSetRenderTargets(1, &normalMapRtv, true, &dsv);

  // Bind the constant buffer for this pass.
  auto passCB = dx_->CurrentFrameRes()->pass_cb->Resource();
  cmd->SetGraphicsRootConstantBufferView(
      ShaderSlot::MAIN_PASS, passCB->GetGPUVirtualAddress());

  cmd->SetPipelineState(GetPipeline("DrawNormals"));

  DrawRenderItems(GetLayer(RenderLayer::Opaque));

  // Change back to GENERIC_READ so we can read the texture in a shader.
  auto to_read = CD3DX12_RESOURCE_BARRIER::Transition(normalMap,
      D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_GENERIC_READ);
  cmd->ResourceBarrier(1, &to_read);
}

void DxDraw::DrawFullscreenQuad() {

  // 绑定顶点与索引
  auto vertex_view = draw_desc_.empty_mesh->VertexBufferView();
  cmd->IASetVertexBuffers(0, 1, &vertex_view);
  auto index_view = draw_desc_.empty_mesh->IndexBufferView();
  cmd->IASetIndexBuffer(&index_view);

  cmd->DrawInstanced(6, 1, 0, 0);
}

void DxDraw::DrawUI() {
  // 设置UI的DSV，需要与其它的DSV独立出来
  auto ds_view = dx_->GetDescriptor().GetDsv(UI_DSV_INDEX);

  // 瞎猫碰到死耗子：教程中一直出错误：
  cmd->ClearDepthStencilView(ds_view,
      D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);
  // Specify the buffers we are going to render to.
  cmd->OMSetRenderTargets(1, &dx_->GetOffscreenRt()->mhCpuRtv, true, &ds_view);

  // 在一切的绘制结束后，绘制UI
  cmd->SetGraphicsRootSignature(dx_->GetRootsign().mUI.Get());
  cmd->SetPipelineState(GetPipeline("UIImage"));

  //
  // 绘制文字
  //
  cmd->SetGraphicsRootDescriptorTable(
      0, dx_->GetDescriptor().GetTextureGpu(draw_desc_.font_index));
  cmd->SetGraphicsRootShaderResourceView(
      1, dx_->CurrentFrameRes()->text_cb->Resource()->GetGPUVirtualAddress());
  dx_->GetUIMesh()->Text()->Draw(dx_->CurrentFrameRes()->text_cb.get(), cmd);

  //
  // 绘制图像
  //
  cmd->SetGraphicsRootDescriptorTable(
      0, dx_->GetDescriptor().GetTextureGpu(draw_desc_.ui_sprite_index));
  cmd->SetGraphicsRootShaderResourceView(
      1, dx_->CurrentFrameRes()->ui_cb->Resource()->GetGPUVirtualAddress());
  dx_->GetUIMesh()->Image()->Draw(dx_->CurrentFrameRes()->ui_cb.get(), cmd);

  // 将默认的rootsign设置回来
  cmd->SetGraphicsRootSignature(dx_->GetRootsign().mStandard.Get());
}

void DxDraw::UpdateWavesGPU() {
  auto waves_disturb = GetPipeline("WavesDisturb");
  auto waves_update = GetPipeline("WavesUpdate");
  // 如果Shader不存在，则不执行
  if (waves_disturb == nullptr || waves_update == nullptr) {
    return;
  }

  // Every quarter second, generate a random wave.
  // 这东西有问题，在锁定帧数为60帧的时候就不会动了。如果不锁定帧数，则是正常的。到时候看。
  static float t_base = 0.0f;
  if ((game::g_timer.TotalTime() - t_base) >= 0.25f) {

    t_base += 0.25f;

    int i = imath::Rand(4, dx_->GetWaves()->RowCount() - 5);
    int j = imath::Rand(4, dx_->GetWaves()->ColumnCount() - 5);

    float r = imath::RandF(1.0f, 2.0f);

    dx_->GetWaves()->Disturb(
        cmd, dx_->GetRootsign().mWaves.Get(), waves_disturb, i, j, r);
  }

  // Update the wave simulation.
  dx_->GetWaves()->SetAllAccess(cmd);
  dx_->GetWaves()->Update(cmd, dx_->GetRootsign().mWaves.Get(), waves_update);
  dx_->GetWaves()->SetAllRead(cmd);
}

ID3D12PipelineState* DxDraw::GetPipeline(const char* name) {
  if (dx_->GetPipeline().IsContainPipeline(name)) {
    return dx_->GetPipeline()[name];
  }
  return nullptr;
}

} // namespace ifire::dx